namespace Caliburn.Micro
{
  using System;
  using System.ComponentModel;
  using System.Linq.Expressions;

  /// <summary>
  /// Extends <see cref="INotifyPropertyChanged"/> such that the change event can be raised by external parties.
  /// </summary>
  public interface INotifyPropertyChangedEx : INotifyPropertyChanged
  {
    /// <summary>
    /// Enables/Disables property change notification.
    /// </summary>
    bool IsNotifying { get; set; }

    /// <summary>
    /// Notifies subscribers of the property change.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    void NotifyOfPropertyChange(string propertyName);

    /// <summary>
    /// Raises a change notification indicating that all bindings should be refreshed.
    /// </summary>
    void Refresh();
  }

#if !SILVERLIGHT
  [Serializable]
#endif
  /// <summary>
  /// A base class that implements the infrastructure for property change notification and automatically performs UI thread marshalling.
  /// </summary>
  public class PropertyChangedBase : INotifyPropertyChangedEx
  {
    /// <summary>
    /// Creates an instance of <see cref="PropertyChangedBase"/>.
    /// </summary>
    public PropertyChangedBase()
    {
      IsNotifying = true;
    }

#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    /// <summary>
    /// Enables/Disables property change notification.
    /// </summary>
    public bool IsNotifying { get; set; }

    /// <summary>
    /// Raises a change notification indicating that all bindings should be refreshed.
    /// </summary>
    public void Refresh()
    {
      NotifyOfPropertyChange(string.Empty);
    }

    /// <summary>
    /// Notifies subscribers of the property change.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    public virtual void NotifyOfPropertyChange(string propertyName)
    {
      if (IsNotifying)
        Execute.OnUIThread(() => RaisePropertyChangedEventCore(propertyName));
    }

    /// <summary>
    /// Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
      NotifyOfPropertyChange(property.GetMemberInfo().Name);
    }

    /// <summary>
    /// Raises the property changed event immediately.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    public virtual void RaisePropertyChangedEventImmediately(string propertyName)
    {
      if (IsNotifying)
        RaisePropertyChangedEventCore(propertyName);
    }

    void RaisePropertyChangedEventCore(string propertyName)
    {
      var handler = PropertyChanged;
      if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}
